defmodule SentryServer do
	@moduledoc """
	谁先发起投票,并且他的term最大,则他为主
	term: 任期,主中控挂了,备+1,选举的时候只有被选举人的term>=自己的term才能投
	"""

	use GenStateMachine, callback_mode: :state_functions
	alias Pango.SwarmUtil
	require Logger
	@timeout 500
	@center_die_timeout 1000


	def start_link(args) do
		GenStateMachine.start_link(__MODULE__, args, name: __MODULE__)
	end

	def can_serve() do
		%{can_serve: can_serve_} = status()
		can_serve_
	end

	def set_waiter_ok() do
		GenStateMachine.cast(__MODULE__, :set_waiter_ok)
	end

	def reset_refresh() do
		GenStateMachine.cast(__MODULE__, :reset_refresh)
	end

	def status() do
		GenStateMachine.call(__MODULE__, :status)
	end

	def get_other_status() do
		case status() do
			%{partner: partners, workers: workers} ->
				Enum.into(
					workers ++ partners,
					%{},
					fn (h) ->
						case SwarmUtil.sync_call(h, {__MODULE__, :status, []}) do
							{:ok, s} ->
								{h, Map.drop(s, [:ref, :partner, :me, :workers, :refresh, :vote_num])}
							_ ->
								{h, %{}}
						end
					end
				)
			_ ->
				"not is center node"
		end
	end

	def vote(swarm, term) do
		GenStateMachine.call(__MODULE__, {:vote, swarm, term})
	end

	def is_master() do
		%{vote: vote, self: self_} = status()
		vote == self_
	end

	def vote_reply(resp) do
		GenStateMachine.cast(__MODULE__, {:vote_reply, resp})
	end

	def ping(swarm, waiter, term) do
		GenStateMachine.call(__MODULE__, {:ping, swarm, waiter, term})
	end

	def waiter_die() do
		GenStateMachine.cast(__MODULE__, :waiter_die)
	end

	def set_waiter(self_) do
		GenStateMachine.cast(__MODULE__, {:set_waiter, self_})
	end

	def init(%{type: :center, partner: partners, me: me} = args) do
		Process.send_after(self(), :timeout, Random.random(100, 400))
		data = MapLib.replace(args, %{refresh: false, ref: nil, vote: nil, waiter: nil, term: 0, partner: partners -- [me]})
		{:ok, :candidate, data}
	end
	def init(args) do
		{:ok, :waiter_elect, MapLib.replace(args, %{can_serve: false, center: nil})}
	end

	def candidate(:info, :timeout, %{term: term} = data) do
		Map.put(data, :term, term + 1)
		|> handle_vote()
	end
	def candidate(msg, event, data) do
		handle_event(msg, event, :candidate, data)
	end
	def center(:info, :ping, data) do
		handle_ping(data)
	end
	def center(:cast, :set_waiter_ok, %{waiter: waiter}) do
		waiter != nil && do_callback(:set_waiter, waiter)
		:keep_state_and_data
	end
	def center(:cast, :waiter_die, data) do
		{:keep_state, %{data | waiter: nil}}
	end
	def center(msg, event, data) do
		handle_event(msg, event, :center, data)
	end
	def follower(:info, :center_die, data) do
		Process.send_after(self(), :timeout, Random.random(50, 100))
		data = %{data | vote: nil, ref: nil}
		{:next_state, :candidate, data}
	end
	def follower(msg, event, data) do
		handle_event(msg, event, :follower, data)
	end

	def waiter_elect(:info, :timeout, %{center: center, me: me} = data) do
		center != nil && SwarmUtil.cast(center, {__MODULE__, :set_waiter, [me]})
		{:next_state, :waiter, data}
	end
	def waiter_elect(msg, event, data) do
		handle_event(msg, event, :waiter_elect, data)
	end
	def waiter(msg, event, data) do
		handle_event(msg, event, :waiter, data)
	end

	def handle_event({:call, from}, {:vote, swarm, term}, _, %{term: my_term, me: me} = data) when my_term < term do
		{:next_state, :follower, %{data | vote: swarm, term: term}, [{:reply, from, %{agree: true, from: me}}]}
	end
	def handle_event({:call, from}, {:vote, _swarm, term}, _, %{term: my_term, me: me}) when my_term > term do
		{:keep_state_and_data, [{:reply, from, %{agree: false, from: me}}]}
	end
	def handle_event({:call, from}, {:vote, swarm, _}, _, %{vote: nil, me: me} = data) do
		data = MapLib.replace(data, %{vote: swarm})
		{:next_state, :follower, data, [{:reply, from, %{agree: true, from: me}}]}
	end
	def handle_event({:call, from}, {:vote, swarm, _}, _, %{vote: vote, me: me} = data) do
		{:keep_state, data, [{:reply, from, %{agree: swarm == vote, from: me}}]}
	end
	# 主中控 ping 备中控
	def handle_event({:call, from}, {:ping, swarm, waiter, term}, current_state, %{type: :center} = data) do
		ref = Map.get(data, :ref)
		ref != nil && Process.cancel_timer(ref)
		ref = Process.send_after(self(), :center_die, @center_die_timeout)
		data = %{data | ref: ref, waiter: waiter, vote: swarm, term: term}
		if current_state == :candidate do
			{:next_state, :follower, data, [{:reply, from, :ok}]}
		else
			{:keep_state, data, [{:reply, from, :ok}]}
		end
	end
	def handle_event({:call, from}, {:ping, center, nil, _term}, _, %{type: :worker, can_serve: true} = data) do
		{:next_state, :waiter_elect, %{data | center: center}, [{:reply, from, :waiter_alive}]}
	end
	def handle_event({:call, from}, {:ping, center, nil, _term}, _, %{type: :worker} = data) do
		Process.send_after(self(), :timeout, Random.random(10, 20))
		{:next_state, :waiter_elect, %{data | center: center, can_serve: false}, [{:reply, from, :ok}]}
	end
	def handle_event({:call, from}, {:ping, center, waiter, _term}, current_state, %{type: :worker, me: me} = data) do
		if current_state == :waiter_elect do
			{:next_state, :waiter, %{data | center: center, can_serve: waiter == me}, [{:reply, from, :ok}]}
		else
			{:keep_state, %{data | center: center, can_serve: waiter == me}, [{:reply, from, :ok}]}
		end
	end
	def handle_event(:cast, {:vote_reply, %{agree: agree}}, :candidate, data) do
		%{vote_num: vote_num, partner: partners, me: me, term: term} = data
		vote_num = (if agree, do: 1, else: 0) + vote_num
		len = (length(partners) + 1) / 2
		if vote_num > len do
			Process.send_after(self(), :ping, 5)
			{:next_state, :center, %{data | vote: me, vote_num: 0, term: term}}
		else
			:keep_state_and_data
		end
	end
	def handle_event(:cast, {:set_waiter, swarm}, :center, %{waiter: nil} = data) do
		do_callback(data, [:set_waiter, swarm])
		{:keep_state, %{data | waiter: swarm}}
	end
	def handle_event(:cast, {:waiter_alive, swarm}, :center, %{waiter: nil} = data) do
		{:keep_state, %{data | waiter: swarm}}
	end
	def handle_event(:cast, :reset_refresh, _current_state, %{refresh: refresh} = data) do
		data = Map.put(data, :refresh, !refresh)
		{:keep_state, data}
	end
	def handle_event({:call, from}, :status, current_state, data) do
		{:keep_state_and_data, [{:reply, from, Map.put(data, :current_state, current_state)}]}
	end
	def handle_event(type, content, current_state, data) do
		str = "Unexpected Event, type: #{inspect(type)}, content: #{inspect(content)}, " <>
		      "current_state: #{current_state}, data: #{inspect(data)}"
		Logger.info(str)
		:keep_state_and_data
	end


	def handle_vote(%{partner: partners, me: me, term: term} = data) do
		parent = self()
		Enum.each(
			partners,
			fn (h) ->
				spawn(
					fn () ->
						case SwarmUtil.sync_call(h, {__MODULE__, :vote, [me, term]}) do
							{:ok, result} ->
								GenStateMachine.cast(parent, {:vote_reply, result})
							error ->
								Random.random(1, 20) < 2 && Logger.error("Call #{h} Vote Error, reason: #{inspect(error)}")
						end
					end
				)
			end
		)
		Process.send_after(self(), :timeout, Random.random(@timeout, @timeout + 1000))
		{:keep_state, MapLib.replace(data, %{vote_num: 1, vote: me})}
	end


	def handle_ping(%{partner: partner, me: me, waiter: waiter, workers: workers, term: term, refresh: refresh} = data) do
		parent = self()
		spawn(
			fn () ->
				try do
					bool = Random.random(1, 100) < 2
					w = case waiter != nil && SwarmUtil.sync_call(waiter, {__MODULE__, :ping, [me, waiter, term]}) do
						:ok ->
							refresh && do_callback(data, [:node_up, waiter])
							waiter
						false ->
							nil
						error ->
							do_callback(data, [:node_down, waiter])
							do_callback(data, [:waiter_die, waiter])
							bool && Logger.error("#{waiter}, Waiter Die, reason: #{inspect(error)}")
							GenStateMachine.cast(parent, :waiter_die)
							nil
					end
					old_waiter = Enum.reduce(
						partner ++ workers,
						nil,
						fn (h, acc) ->
							case SwarmUtil.sync_call(h, {__MODULE__, :ping, [me, w, term]}) do
								{:ok, :waiter_alive} ->
									refresh && do_callback(data, [:node_up, h])
									h
								:ok ->
									refresh && do_callback(data, [:node_up, h])
									acc
								error ->
									do_callback(data, [:node_down, h])
									bool && Logger.error("Ping #{h} Error, reason: #{inspect(error)}")
									acc
							end
						end
					)
					if w == nil do
						if old_waiter != nil do
							GenStateMachine.cast(parent, {:waiter_alive, old_waiter})
						else
							waiter != nil && GenStateMachine.cast(parent, :waiter_die)
						end
					end
				rescue
					reason ->
						Logger.error("reason: #{inspect(reason)}, stacktrace: #{inspect(__STACKTRACE__)}")
				catch
					{:error, _error} ->
						:ok
					_error ->
						:ok
				end
				Process.send_after(parent, :ping, @timeout)
			end
		)
		:keep_state_and_data
	end

	defp do_callback(%{callback: callback}, msg) do
		spawn(
			fn () ->
				case callback do
					{m, f} ->
						apply(m, f, msg)
					{m, f, a} ->
						apply(m, f, a ++ msg)
					_ ->
						:ok
				end
			end
		)
	end
	defp do_callback(_, _) do
		:ok
	end


end
